﻿// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.

using UnityEngine;
using System.Text;
using System.Collections.Generic;

namespace CWM.Skinn
{
    public static partial class SkinnEx
    {
        public static string ToACSIIFBX(this SkinnedMeshRenderer source, bool optimizeBones = false)
        {
            if (source == null || !source.HasMinimumRequirements(true)) return "";
            if (IsNullOrEmpty(source.bones))
            {
                Debug.LogWarningFormat("{0} has an empty bone array. export failed!", source);
                return "";
            }
            if (source.bones.HasAnyNulls())
            {
                Debug.LogWarningFormat(" {0} has bones that are null in the bone array. export failed!", source);
                return "";
            }

            return ACSIIExporter.ToFBX(source, optimizeBones);
        }
       
        public static string ToACSIIFBX(this MeshFilter source)
        {
            if (source == null || source.sharedMesh == null || !source.sharedMesh.HasMinimumRequirements()) return "";
            return ACSIIExporter.ToFBX(source);
        }

        public static string ToACSIIFBX(this MeshRenderer source)
        {
            return ToACSIIFBX(source.GetComponent<MeshFilter>());
        }

        public static string ToACSIIFBX(this GameObject source, bool addTempMesh = false)
        {
            return ACSIIExporter.ToFBX(source, addTempMesh);
        }

        private static class ACSIIExporter
        {
            private enum ModelType { Root, Mesh, LimbNode, NULL }

            private const string MissingMaterialPrefix = "Material";


            private static StringBuilder fbx;
            private static StringBuilder objects;
            private static StringBuilder connections;

            public static string ToFBX(SkinnedMeshRenderer smr, bool optimizeTransforms = false)
            {
                fbx = new StringBuilder();

                var hasBlendshapes = smr.sharedMesh.blendShapeCount > 0 ? true : false;
                var hasSkin = smr.HasMinimumRequirements();

                fbx = FBX2013Template((hasBlendshapes || hasSkin ? true : false));

                objects = new StringBuilder();
                objects.AppendLine("; Object properties");
                objects.AppendLine(";------------------------------------------------------------------");
                objects.AppendLine("");
                objects.AppendLine("Objects:  {");

                connections = new StringBuilder();
                connections.AppendLine("; Object connections");
                connections.AppendLine(";------------------------------------------------------------------");
                connections.AppendLine("");
                connections.AppendLine("Connections:  {");
                connections.AppendLine("\t");

                GameObject tempGameObject = null;
                GameObject root = smr.transform.parent ? smr.transform.root.gameObject : null;
                if (!root)
                {
                    tempGameObject = new GameObject(EnforceString(smr.name) + " " + GetUniqueID().Remove(4));
                    root = tempGameObject;
                }

                var rootNodeID = ModelNode(root, ModelType.Root);
                var modelNodeID = ModelNode(smr.gameObject, ModelType.Mesh, rootNodeID);
                var geometryNodeID = GeometryNode(smr.sharedMesh, modelNodeID);

                var sharedMesh = smr.sharedMesh;
                var sharedMaterials = smr.sharedMaterials;
                int subMeshCount = sharedMesh.subMeshCount;
                for (int i = 0; i < subMeshCount; i++)
                {
                    string materialName = sharedMesh.subMeshCount == 1 ? MissingMaterialPrefix : MissingMaterialPrefix + "_" + i.ToString();
                    if ((sharedMaterials.Length > i) & sharedMaterials[i] != null) materialName = sharedMaterials[i].name;
                    MaterialNode(modelNodeID, materialName, smr.gameObject.name);
                }

                if (hasBlendshapes) MorpherNode(sharedMesh, geometryNodeID);

                if (!hasSkin) return Finalize();
                
                string[] boneIDs;
                TransformNodes(root.transform, smr, rootNodeID, out boneIDs, optimizeTransforms);
                SkinNode(smr, geometryNodeID, boneIDs);

                Release(tempGameObject);
                return Finalize();
            }

            public static string ToFBX(MeshFilter mf)
            {
                bool hasBlendshapes = mf.sharedMesh.blendShapeCount > 0 ? true : false;
                fbx = FBX2013Template(hasBlendshapes);

                objects = new StringBuilder();
                objects.AppendLine("; Object properties");
                objects.AppendLine(";------------------------------------------------------------------");
                objects.AppendLine("");
                objects.AppendLine("Objects:  {");

                connections = new StringBuilder();
                connections.AppendLine("; Object connections");
                connections.AppendLine(";------------------------------------------------------------------");
                connections.AppendLine("");
                connections.AppendLine("Connections:  {");
                connections.AppendLine("\t");

                string modelNodeID = ModelNode(mf.gameObject, ModelType.Mesh);
                string geometryNodeID = GeometryNode(mf.sharedMesh, modelNodeID);

                Mesh sharedMesh = mf.sharedMesh;
                int subMeshCount = sharedMesh.subMeshCount;
                Material[] sharedMaterials = mf.GetComponent<Renderer>().GetSharedMaterialsFromSubMeshes();
                for (int i = 0; i < subMeshCount; i++)
                {
                    string materialName = subMeshCount == 1 ? MissingMaterialPrefix : MissingMaterialPrefix + "_" + i.ToString();
                    if ((sharedMaterials.Length > i) & sharedMaterials[i] != null) materialName = sharedMaterials[i].name;
                    MaterialNode(modelNodeID, materialName, mf.gameObject.name);
                }

                if (hasBlendshapes) MorpherNode(sharedMesh, geometryNodeID);
                return Finalize();
            }

            public static string ToFBX(GameObject root)
            {
                fbx = FBX2013Template();
                objects = new StringBuilder();
                objects.AppendLine("; Object properties");
                objects.AppendLine(";------------------------------------------------------------------");
                objects.AppendLine("");
                objects.AppendLine("Objects:  {");

                connections = new StringBuilder();
                connections.AppendLine("; Object connections");
                connections.AppendLine(";------------------------------------------------------------------");
                connections.AppendLine("");
                connections.AppendLine("Connections:  {");
                connections.AppendLine("\t");

                string id = ModelNode(root, ModelType.Root);
                AddNodesRecursive(root.transform, id);
                return Finalize();
            }

            public static string ToFBX(GameObject root, bool tempMesh)
            {
                if (!tempMesh) return ToFBX(root);
                fbx = FBX2013Template();
                objects = new StringBuilder();
                objects.AppendLine("; Object properties");
                objects.AppendLine(";------------------------------------------------------------------");
                objects.AppendLine("");
                objects.AppendLine("Objects:  {");

                connections = new StringBuilder();
                connections.AppendLine("; Object connections");
                connections.AppendLine(";------------------------------------------------------------------");
                connections.AppendLine("");
                connections.AppendLine("Connections:  {");
                connections.AppendLine("\t");

                string id = ModelNode(root, ModelType.Root);
                var sharedMesh = SkinnGizmos.DynamicCube(Color.white, Vector3.one, Vector3.one, (1f).ToUnitFromStandard(ConversionUnits.Inch));
                sharedMesh.RecalculateNormals();
                GeometryNode(sharedMesh, id);
                SkinnGizmos.Release();

                string materialName = MissingMaterialPrefix;
                MaterialNode(id, materialName, EnforceString(root.name));

                AddNodesRecursive(root.transform, id);
                return Finalize();
            }

            private static void AddNodesRecursive(Transform transform, string parentID)
            {
                for (int i = 0; i < transform.childCount; ++i)
                {
                    var child = transform.GetChild(i);
                    string parent = ModelNode(child.gameObject, ModelType.NULL, parentID);
                    AddNodesRecursive(child, parent);
                }
            }

            private static StringBuilder FBX2013Template(bool hasDeformer = false)
            {
                StringBuilder sb = new StringBuilder();

                #region "FBX Copyright (C)"
                sb.AppendLine("; FBX 7.3.0 project file");
                sb.AppendLine("; Copyright (C) 1997-2010 Autodesk Inc. and/or its licensors.");
                sb.AppendLine("; All rights reserved.");
                sb.AppendLine("; ----------------------------------------------------");
                sb.AppendLine();
                #endregion
                #region "FBXVersion"
                sb.AppendLine("FBXHeaderExtension:  {");
                sb.AppendLine("\tFBXHeaderVersion: 1003");
                sb.AppendLine("\tFBXVersion: 7300");
                #endregion
                #region "TimeStamp"
                System.DateTime cTime = System.DateTime.Now;
                sb.AppendLine("\tCreationTimeStamp:  {");
                sb.AppendLine("\t\tVersion: 1000");
                sb.AppendLine("\t\tYear: " + cTime.Year);
                sb.AppendLine("\t\tMonth: " + cTime.Month);
                sb.AppendLine("\t\tDay: " + cTime.Day);
                sb.AppendLine("\t\tHour: " + cTime.Hour);
                sb.AppendLine("\t\tMinute: " + cTime.Minute);
                sb.AppendLine("\t\tSecond: " + cTime.Second);
                sb.AppendLine("\t\tMillisecond: " + cTime.Millisecond);
                sb.AppendLine("\t}");
                #endregion
                #region "UserData"
                sb.AppendLine("\tCreator: \"C Sharp\"");
                sb.AppendLine("\tSceneInfo: \"SceneInfo::GlobalInfo\", \"UserData\" {");
                sb.AppendLine("\t\tType: \"UserData\"");
                sb.AppendLine("\t\tVersion: 100");
                sb.AppendLine("\t\tMetaData:  {");
                sb.AppendLine("\t\t\tVersion: 100");
                sb.AppendLine("\t\t\tTitle: \"\"");
                sb.AppendLine("\t\t\tSubject: \"\"");
                sb.AppendLine("\t\t\tAuthor: \"\"");
                sb.AppendLine("\t\t\tKeywords: \"\"");
                sb.AppendLine("\t\t\tRevision: \"\"");
                sb.AppendLine("\t\t\tComment: \"\"");
                sb.AppendLine("\t\t}");
                sb.AppendLine("\t\tProperties70:  {");
                #endregion
                #region "Document"
                sb.AppendLine("\t\t\tP: \"DocumentUrl\", \"KString\", \"Url\", \"\", \"Null\"");
                sb.AppendLine("\t\t\tP: \"SrcDocumentUrl\", \"KString\", \"Url\", \"\", \"Null\"");
                sb.AppendLine("\t\t\tP: \"Original\", \"Compound\", \"\", \"\"");
                sb.AppendLine("\t\t\tP: \"Original|ApplicationVendor\", \"KString\", \"\", \"\", \"\"");
                sb.AppendLine("\t\t\tP: \"Original|ApplicationName\", \"KString\", \"\", \"\", \"\"");
                sb.AppendLine("\t\t\tP: \"Original|ApplicationVersion\", \"KString\", \"\", \"\", \"\"");
                sb.AppendLine("\t\t\tP: \"Original|DateTime_GMT\", \"DateTime\", \"\", \"\", \"\"");
                sb.AppendLine("\t\t\tP: \"Original|FileName\", \"KString\", \"\", \"\", \"\"");
                sb.AppendLine("\t\t\tP: \"LastSaved\", \"Compound\", \"\", \"\"");
                sb.AppendLine("\t\t\tP: \"LastSaved|ApplicationVendor\", \"KString\", \"\", \"\", \"\"");
                sb.AppendLine("\t\t\tP: \"LastSaved|ApplicationName\", \"KString\", \"\", \"\", \"\"");
                sb.AppendLine("\t\t\tP: \"LastSaved|ApplicationVersion\", \"KString\", \"\", \"\", \"\"");
                sb.AppendLine("\t\t\tP: \"LastSaved|DateTime_GMT\", \"DateTime\", \"\", \"\", \"\"");
                sb.AppendLine("\t\t}");
                sb.AppendLine("\t}");
                sb.AppendLine("}");
                #endregion
                #region "GlobalSettings"
                sb.AppendLine("GlobalSettings:  {");
                sb.AppendLine("\tVersion: 1000");
                sb.AppendLine("\tProperties70:  {");
                sb.AppendLine("\t\tP: \"UpAxis\", \"int\", \"Integer\", \"\",1");
                sb.AppendLine("\t\tP: \"UpAxisSign\", \"int\", \"Integer\", \"\",1");
                sb.AppendLine("\t\tP: \"FrontAxis\", \"int\", \"Integer\", \"\",2");
                sb.AppendLine("\t\tP: \"FrontAxisSign\", \"int\", \"Integer\", \"\",1");
                sb.AppendLine("\t\tP: \"CoordAxis\", \"int\", \"Integer\", \"\",0");
                sb.AppendLine("\t\tP: \"CoordAxisSign\", \"int\", \"Integer\", \"\",1");
                sb.AppendLine("\t\tP: \"OriginalUpAxis\", \"int\", \"Integer\", \"\",-1");
                sb.AppendLine("\t\tP: \"OriginalUpAxisSign\", \"int\", \"Integer\", \"\",1");
                sb.AppendLine("\t\tP: \"UnitScaleFactor\", \"double\", \"Number\", \"\",100");
                sb.AppendLine("\t\tP: \"OriginalUnitScaleFactor\", \"double\", \"Number\", \"\",100");
                sb.AppendLine("\t\tP: \"AmbientColor\", \"ColorRGB\", \"Color\", \"\",0,0,0");
                sb.AppendLine("\t\tP: \"DefaultCamera\", \"KString\", \"\", \"\", \"Producer Perspective\"");
                sb.AppendLine("\t\tP: \"TimeMode\", \"enum\", \"\", \"\",11");
                sb.AppendLine("\t\tP: \"TimeSpanStart\", \"KTime\", \"Time\", \"\",0");
                sb.AppendLine("\t\tP: \"TimeSpanStop\", \"KTime\", \"Time\", \"\",479181389250");
                sb.AppendLine("\t\tP: \"CustomFrameRate\", \"double\", \"Number\", \"\",-1");
                sb.AppendLine("\t}");
                sb.AppendLine("}");
                #endregion
                #region "Object Definitions"
                sb.AppendLine("; Object definitions");
                sb.AppendLine(";------------------------------------------------------------------");
                sb.AppendLine("");
                sb.AppendLine("Definitions:  {");
                sb.AppendLine("\tVersion: 100");
                sb.AppendLine("\tCount: 1");
                #endregion
                #region "GlobalSettings"
                sb.AppendLine("\tObjectType: \"GlobalSettings\" {");
                sb.AppendLine("\t\tCount: 1");
                sb.AppendLine("\t}");
                #endregion
                #region "Model"
                sb.AppendLine("\tObjectType: \"Model\" {");
                sb.AppendLine("\t\tCount: 1");
                sb.AppendLine("\t\tPropertyTemplate: \"FbxNode\" {");
                sb.AppendLine("\t\t\tProperties70:  {");
                sb.AppendLine("\t\t\t\tP: \"QuaternionInterpolate\", \"enum\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"RotationOffset\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"RotationPivot\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"ScalingOffset\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"ScalingPivot\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"TranslationActive\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"TranslationMin\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"TranslationMax\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"TranslationMinX\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"TranslationMinY\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"TranslationMinZ\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"TranslationMaxX\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"TranslationMaxY\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"TranslationMaxZ\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"RotationOrder\", \"enum\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"RotationSpaceForLimitOnly\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"RotationStiffnessX\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"RotationStiffnessY\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"RotationStiffnessZ\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"AxisLen\", \"double\", \"Number\", \"\",10");
                sb.AppendLine("\t\t\t\tP: \"PreRotation\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"PostRotation\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"RotationActive\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"RotationMin\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"RotationMax\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"RotationMinX\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"RotationMinY\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"RotationMinZ\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"RotationMaxX\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"RotationMaxY\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"RotationMaxZ\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"InheritType\", \"enum\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"ScalingActive\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"ScalingMin\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"ScalingMax\", \"Vector3D\", \"Vector\", \"\",1,1,1");
                sb.AppendLine("\t\t\t\tP: \"ScalingMinX\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"ScalingMinY\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"ScalingMinZ\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"ScalingMaxX\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"ScalingMaxY\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"ScalingMaxZ\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"GeometricTranslation\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"GeometricRotation\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"GeometricScaling\", \"Vector3D\", \"Vector\", \"\",1,1,1");
                sb.AppendLine("\t\t\t\tP: \"MinDampRangeX\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"MinDampRangeY\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"MinDampRangeZ\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"MaxDampRangeX\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"MaxDampRangeY\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"MaxDampRangeZ\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"MinDampStrengthX\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"MinDampStrengthY\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"MinDampStrengthZ\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"MaxDampStrengthX\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"MaxDampStrengthY\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"MaxDampStrengthZ\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"PreferedAngleX\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"PreferedAngleY\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"PreferedAngleZ\", \"double\", \"Number\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"LookAtProperty\", \"object\", \"\", \"\"");
                sb.AppendLine("\t\t\t\tP: \"UpVectorProperty\", \"object\", \"\", \"\"");
                sb.AppendLine("\t\t\t\tP: \"Show\", \"bool\", \"\", \"\",1");
                sb.AppendLine("\t\t\t\tP: \"NegativePercentShapeSupport\", \"bool\", \"\", \"\",1");
                sb.AppendLine("\t\t\t\tP: \"DefaultAttributeIndex\", \"int\", \"Integer\", \"\",-1");
                sb.AppendLine("\t\t\t\tP: \"Freeze\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"LODBox\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"Lcl Translation\", \"Lcl Translation\", \"\", \"A\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"Lcl Rotation\", \"Lcl Rotation\", \"\", \"A\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"Lcl Scaling\", \"Lcl Scaling\", \"\", \"A\",1,1,1");
                sb.AppendLine("\t\t\t\tP: \"Visibility\", \"Visibility\", \"\", \"A\",1");
                sb.AppendLine("\t\t\t\tP: \"Visibility Inheritance\", \"Visibility Inheritance\", \"\", \"\",1");
                sb.AppendLine("\t\t\t}");
                sb.AppendLine("\t\t}");
                sb.AppendLine("\t}");
                #endregion
                #region "Geometry"
                sb.AppendLine("\tObjectType: \"Geometry\" {");
                sb.AppendLine("\t\tCount: 1");
                sb.AppendLine("\t\tPropertyTemplate: \"FbxMesh\" {");
                sb.AppendLine("\t\t\tProperties70:  {");
                sb.AppendLine("\t\t\t\tP: \"Color\", \"ColorRGB\", \"Color\", \"\",0.8,0.8,0.8");
                sb.AppendLine("\t\t\t\tP: \"BBoxMin\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"BBoxMax\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"Primary Visibility\", \"bool\", \"\", \"\",1");
                sb.AppendLine("\t\t\t\tP: \"Casts Shadows\", \"bool\", \"\", \"\",1");
                sb.AppendLine("\t\t\t\tP: \"Receive Shadows\", \"bool\", \"\", \"\",1");
                sb.AppendLine("\t\t\t}");
                sb.AppendLine("\t\t}");
                sb.AppendLine("\t}");
                #endregion
                #region "Material"
                sb.AppendLine("\tObjectType: \"Material\" {");
                sb.AppendLine("\t\tCount: 1");
                sb.AppendLine("\t\tPropertyTemplate: \"FbxSurfacePhong\" {");
                sb.AppendLine("\t\t\tProperties70:  {");
                sb.AppendLine("\t\t\t\tP: \"ShadingModel\", \"KString\", \"\", \"\", \"Phong\"");
                sb.AppendLine("\t\t\t\tP: \"MultiLayer\", \"bool\", \"\", \"\",0");
                sb.AppendLine("\t\t\t\tP: \"EmissiveColor\", \"Color\", \"\", \"A\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"EmissiveFactor\", \"Number\", \"\", \"A\",1");
                sb.AppendLine("\t\t\t\tP: \"AmbientColor\", \"Color\", \"\", \"A\",0.2,0.2,0.2");
                sb.AppendLine("\t\t\t\tP: \"AmbientFactor\", \"Number\", \"\", \"A\",1");
                sb.AppendLine("\t\t\t\tP: \"DiffuseColor\", \"Color\", \"\", \"A\",0.8,0.8,0.8");
                sb.AppendLine("\t\t\t\tP: \"DiffuseFactor\", \"Number\", \"\", \"A\",1");
                sb.AppendLine("\t\t\t\tP: \"Bump\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"NormalMap\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"BumpFactor\", \"double\", \"Number\", \"\",1");
                sb.AppendLine("\t\t\t\tP: \"TransparentColor\", \"Color\", \"\", \"A\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"TransparencyFactor\", \"Number\", \"\", \"A\",0");
                sb.AppendLine("\t\t\t\tP: \"DisplacementColor\", \"ColorRGB\", \"Color\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"DisplacementFactor\", \"double\", \"Number\", \"\",1");
                sb.AppendLine("\t\t\t\tP: \"VectorDisplacementColor\", \"ColorRGB\", \"Color\", \"\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"VectorDisplacementFactor\", \"double\", \"Number\", \"\",1");
                sb.AppendLine("\t\t\t\tP: \"SpecularColor\", \"Color\", \"\", \"A\",0.2,0.2,0.2");
                sb.AppendLine("\t\t\t\tP: \"SpecularFactor\", \"Number\", \"\", \"A\",1");
                sb.AppendLine("\t\t\t\tP: \"ShininessExponent\", \"Number\", \"\", \"A\",20");
                sb.AppendLine("\t\t\t\tP: \"ReflectionColor\", \"Color\", \"\", \"A\",0,0,0");
                sb.AppendLine("\t\t\t\tP: \"ReflectionFactor\", \"Number\", \"\", \"A\",1");
                sb.AppendLine("\t\t\t}");
                sb.AppendLine("\t\t}");
                sb.AppendLine("\t}");
                #endregion

                if (hasDeformer)
                {
                    sb.AppendLine("\tObjectType: \"Deformer\" {");
                    sb.AppendLine("\t\tCount: 1");
                    sb.AppendLine("\t}");
                }

                sb.AppendLine("}");
                sb.AppendLine("");

                return sb;
            }

            private static string Finalize()
            {
                objects.AppendLine("}");
                connections.AppendLine("}");

                fbx.Append(objects.ToString());
                fbx.Append(connections.ToString());

                var fbxText = fbx.ToString();

                objects = null;
                connections = null;
                fbx = null;

                return fbxText;
            }

            private static string ModelNode(GameObject gameObject, ModelType modelType = ModelType.Root, string parent = "", string overrideID = "", string overideName = "")
            {
                string id = (overrideID == "" ? GetUniqueID32() : overrideID);
                string name = (overideName == "" ? gameObject.name : overideName);
                StringBuilder currentObject = new StringBuilder();
                string objectType, connectionType;
                switch (modelType)
                {
                    case ModelType.Root:
                        objectType = "NULL";
                        connectionType = "RootNode";
                        break;
                    case ModelType.Mesh:
                        objectType = "Mesh";
                        connectionType = "USING PARENT";
                        break;
                    case ModelType.LimbNode:
                        objectType = "LimbNode";
                        connectionType = "USING PARENT";
                        break;
                    case ModelType.NULL:
                    default:
                        objectType = "NULL";
                        connectionType = "USING PARENT";
                        break;
                }

                #region "Object"
                currentObject.AppendLine("\tModel: " + id + ", \"Model::" + name + "\", \"" + objectType + "\" {");
                currentObject.AppendLine("\t\tVersion: 232");
                currentObject.AppendLine("\t\tProperties70:  {");
                currentObject.AppendLine("\t\t\tP: \"RotationOrder\", \"enum\", \"\", \"\",4");
                currentObject.AppendLine("\t\t\tP: \"RotationActive\", \"bool\", \"\", \"\",1");
                currentObject.AppendLine("\t\t\tP: \"InheritType\", \"enum\", \"\", \"\",1");
                currentObject.AppendLine("\t\t\tP: \"ScalingMax\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                currentObject.AppendLine("\t\t\tP: \"DefaultAttributeIndex\", \"int\", \"Integer\", \"\",0");
                currentObject.Append("\t\t\tP: \"Lcl Translation\", \"Lcl Translation\", \"\", \"A+\",");

                Vector3 localPosition = gameObject.transform.localPosition;
                currentObject.AppendFormat("{0},{1},{2}", localPosition.x * -1, localPosition.y, localPosition.z);
                currentObject.AppendLine();

                Vector3 localEulerAngles = gameObject.transform.localEulerAngles;
                currentObject.AppendFormat("\t\t\tP: \"Lcl Rotation\", \"Lcl Rotation\", \"\", \"A+\",{0},{1},{2}", localEulerAngles.x, localEulerAngles.y * -1, -1 * localEulerAngles.z);
                currentObject.AppendLine();

                Vector3 localScale = gameObject.transform.localScale;
                currentObject.AppendFormat("\t\t\tP: \"Lcl Scaling\", \"Lcl Scaling\", \"\", \"A\",{0},{1},{2}", localScale.x, localScale.y, localScale.z);
                currentObject.AppendLine();

                currentObject.AppendLine("\t\t}");
                currentObject.AppendLine("\t\tShading: T");
                currentObject.AppendLine("\t\tCulling: \"CullingOff\"");
                currentObject.AppendLine("\t}");
                #endregion
                //Connection
                StringBuilder currentConnection = new StringBuilder();

                currentConnection.AppendLine("\t;Model::" + name + ", Model::" + connectionType);
                currentConnection.AppendLine("\tC: \"OO\"," + id + "," + (parent == "" ? "0" : parent));
                currentConnection.AppendLine();

                objects.Append(currentObject.ToString());
                connections.Append(currentConnection.ToString());
                return id;
            }

            private static string GeometryNode(Mesh mesh, string parent)
            {
                string id = GetUniqueID32();
                StringBuilder currentObject = new StringBuilder();
                currentObject.AppendLine("\tGeometry: " + id + ", \"Geometry::\", \"Mesh\" {");

                #region"Vertices"
                Vector3[] vertices = mesh.vertices;
                int vertCount = mesh.vertices.Length * 3;

                currentObject.AppendLine("\t\tVertices: *" + vertCount + " {");
                currentObject.Append("\t\t\ta: ");
                for (int i = 0; i < vertices.Length; i++)
                {
                    if (i > 0) currentObject.Append(",");
                    currentObject.AppendFormat("{0},{1},{2}", vertices[i].x * -1, vertices[i].y, vertices[i].z);
                }

                currentObject.AppendLine();
                currentObject.AppendLine("\t\t} ");
                #endregion

                #region "Triangles"
                int triangleCount = mesh.triangles.Length;
                int[] triangles = mesh.triangles;

                currentObject.AppendLine("\t\tPolygonVertexIndex: *" + triangleCount + " {");

                currentObject.Append("\t\t\ta: ");
                for (int i = 0; i < triangleCount; i += 3)
                {
                    if (i > 0) currentObject.Append(",");
                    currentObject.AppendFormat("{0},{1},{2}", triangles[i], triangles[i + 2], (triangles[i + 1] * -1) - 1);
                }

                currentObject.AppendLine();
                currentObject.AppendLine("\t\t} ");
                currentObject.AppendLine("\t\tGeometryVersion: 124");
                #endregion

                #region "Normals"
                currentObject.AppendLine("\t\tLayerElementNormal: 0 {");
                currentObject.AppendLine("\t\t\tVersion: 101");
                currentObject.AppendLine("\t\t\tName: \"\"");
                currentObject.AppendLine("\t\t\tMappingInformationType: \"ByPolygonVertex\"");
                currentObject.AppendLine("\t\t\tReferenceInformationType: \"Direct\"");

                Vector3[] normals = mesh.GetNormalsRecalculateIfNeeded();

                currentObject.AppendLine("\t\t\tNormals: *" + (triangleCount * 3) + " {");
                currentObject.Append("\t\t\t\ta: ");
                for (int i = 0; i < triangleCount; i += 3)
                {
                    if (i > 0) currentObject.Append(",");
                    Vector3 newNormal = normals[triangles[i]];
                    currentObject.AppendFormat("{0},{1},{2},", newNormal.x * -1, newNormal.y, newNormal.z);
                    newNormal = normals[triangles[i + 2]];
                    currentObject.AppendFormat("{0},{1},{2},", newNormal.x * -1, newNormal.y, newNormal.z);
                    newNormal = normals[triangles[i + 1]];
                    currentObject.AppendFormat("{0},{1},{2}", newNormal.x * -1, newNormal.y, newNormal.z);
                }

                currentObject.AppendLine();
                currentObject.AppendLine("\t\t\t}");
                currentObject.AppendLine("\t\t}");
                #endregion

                #region "Colors"

                bool hasColors = IsNullOrEmpty(mesh.colors32) ? false : true;

                if (hasColors)
                {
                    int colorsCount = mesh.colors32.Length;

                    currentObject.AppendLine("\t\tLayerElementColor: 0 {");
                    currentObject.AppendLine("\t\t\tVersion: 101");
                    currentObject.AppendLine("\t\t\tName: \"\"");
                    currentObject.AppendLine("\t\t\tMappingInformationType: \"ByPolygonVertex\"");
                    currentObject.AppendLine("\t\t\tReferenceInformationType: \"IndexToDirect\"");

                    Color32[] colors = mesh.colors32;

                    currentObject.AppendLine("\t\t\tColors: *" + (colorsCount * 4) + " {");
                    currentObject.Append("\t\t\t\ta: ");

                    for (int i = 0; i < colorsCount; i++)
                    {
                        if (i > 0) currentObject.Append(",");
                        Color clr = colors[i];
                        currentObject.AppendFormat("{0},{1},{2},{3}", clr.r, clr.g, clr.b, clr.a);
                    }
                    currentObject.AppendLine();
                    currentObject.AppendLine("\t\t\t}");

                    currentObject.AppendLine("\t\t\tColorIndex: *" + triangleCount + " {");
                    currentObject.Append("\t\t\t\ta: ");

                    for (int i = 0; i < triangleCount; i += 3)
                    {
                        if (i > 0) currentObject.Append(",");
                        currentObject.AppendFormat("{0},{1},{2}", triangles[i], triangles[i + 2], triangles[i + 1]);
                    }

                    currentObject.AppendLine();

                    currentObject.AppendLine("\t\t\t}");

                    currentObject.AppendLine("\t\t}");
                }
                #endregion

                #region "UV0"
                bool uv0 = (mesh.uv != null) & mesh.uv.Length > 0;
                if (uv0)
                {
                    int uvLength = mesh.uv.Length;
                    Vector2[] uvs = mesh.uv;

                    currentObject.AppendLine("\t\tLayerElementUV: 0 {");
                    currentObject.AppendLine("\t\t\tVersion: 101");
                    currentObject.AppendLine("\t\t\tName: \"UVChannel_1\"");
                    currentObject.AppendLine("\t\t\tMappingInformationType: \"ByPolygonVertex\"");
                    currentObject.AppendLine("\t\t\tReferenceInformationType: \"IndexToDirect\"");
                    currentObject.AppendLine("\t\t\tUV: *" + uvLength * 2 + " {");
                    currentObject.Append("\t\t\t\ta: ");

                    for (int i = 0; i < uvLength; i++)
                    {
                        if (i > 0)
                            currentObject.Append(",");

                        currentObject.AppendFormat("{0},{1}", uvs[i].x, uvs[i].y);
                    }

                    currentObject.AppendLine();
                    currentObject.AppendLine("\t\t\t\t}");

                    currentObject.AppendLine("\t\t\tUVIndex: *" + triangleCount + " {");
                    currentObject.Append("\t\t\t\ta: ");

                    for (int i = 0; i < triangleCount; i += 3)
                    {
                        if (i > 0)
                            currentObject.Append(",");

                        currentObject.AppendFormat("{0},{1},{2}", triangles[i], triangles[i + 2], triangles[i + 1]);
                    }

                    currentObject.AppendLine();

                    currentObject.AppendLine("\t\t\t}");
                    currentObject.AppendLine("\t\t}");
                }
                #endregion

                #region "UV1"
                bool uv1 = (mesh.uv2 != null) & mesh.uv2.Length > 0;
                if (uv1)
                {
                    int uvLength = mesh.uv2.Length;
                    Vector2[] uvs = mesh.uv2;

                    currentObject.AppendLine("\t\tLayerElementUV: 1 {");
                    currentObject.AppendLine("\t\t\tVersion: 101");
                    currentObject.AppendLine("\t\t\tName: \"UVChannel_2\"");
                    currentObject.AppendLine("\t\t\tMappingInformationType: \"ByPolygonVertex\"");
                    currentObject.AppendLine("\t\t\tReferenceInformationType: \"IndexToDirect\"");
                    currentObject.AppendLine("\t\t\tUV: *" + uvLength * 2 + " {");
                    currentObject.Append("\t\t\t\ta: ");

                    for (int i = 0; i < uvLength; i++)
                    {
                        if (i > 0)
                            currentObject.Append(",");

                        currentObject.AppendFormat("{0},{1}", uvs[i].x, uvs[i].y);
                    }

                    currentObject.AppendLine();
                    currentObject.AppendLine("\t\t\t\t}");

                    currentObject.AppendLine("\t\t\tUVIndex: *" + triangleCount + " {");
                    currentObject.Append("\t\t\t\ta: ");

                    for (int i = 0; i < triangleCount; i += 3)
                    {
                        if (i > 0)
                            currentObject.Append(",");

                        currentObject.AppendFormat("{0},{1},{2}", triangles[i], triangles[i + 2], triangles[i + 1]);
                    }

                    currentObject.AppendLine();

                    currentObject.AppendLine("\t\t\t}");
                    currentObject.AppendLine("\t\t}");
                }
                #endregion

                #region "UV2"
                bool uv2 = (mesh.uv3 != null) & mesh.uv3.Length > 0;
                if (uv2)
                {
                    int uvLength = mesh.uv3.Length;
                    Vector2[] uvs = mesh.uv3;

                    currentObject.AppendLine("\t\tLayerElementUV: 2 {");
                    currentObject.AppendLine("\t\t\tVersion: 101");
                    currentObject.AppendLine("\t\t\tName: \"UVChannel_3\"");
                    currentObject.AppendLine("\t\t\tMappingInformationType: \"ByPolygonVertex\"");
                    currentObject.AppendLine("\t\t\tReferenceInformationType: \"IndexToDirect\"");
                    currentObject.AppendLine("\t\t\tUV: *" + uvLength * 2 + " {");
                    currentObject.Append("\t\t\t\ta: ");

                    for (int i = 0; i < uvLength; i++)
                    {
                        if (i > 0)
                            currentObject.Append(",");

                        currentObject.AppendFormat("{0},{1}", uvs[i].x, uvs[i].y);
                    }

                    currentObject.AppendLine();
                    currentObject.AppendLine("\t\t\t\t}");

                    currentObject.AppendLine("\t\t\tUVIndex: *" + triangleCount + " {");
                    currentObject.Append("\t\t\t\ta: ");

                    for (int i = 0; i < triangleCount; i += 3)
                    {
                        if (i > 0)
                            currentObject.Append(",");

                        currentObject.AppendFormat("{0},{1},{2}", triangles[i], triangles[i + 2], triangles[i + 1]);
                    }

                    currentObject.AppendLine();

                    currentObject.AppendLine("\t\t\t}");
                    currentObject.AppendLine("\t\t}");
                }
                #endregion

                #region "UV3"
                bool uv3 = (mesh.uv4 != null) & mesh.uv4.Length > 0;
                if (uv3)
                {
                    int uvLength = mesh.uv4.Length;
                    Vector2[] uvs = mesh.uv4;

                    currentObject.AppendLine("\t\tLayerElementUV: 3 {");
                    currentObject.AppendLine("\t\t\tVersion: 101");
                    currentObject.AppendLine("\t\t\tName: \"UVChannel_4\"");
                    currentObject.AppendLine("\t\t\tMappingInformationType: \"ByPolygonVertex\"");
                    currentObject.AppendLine("\t\t\tReferenceInformationType: \"IndexToDirect\"");
                    currentObject.AppendLine("\t\t\tUV: *" + uvLength * 2 + " {");
                    currentObject.Append("\t\t\t\ta: ");

                    for (int i = 0; i < uvLength; i++)
                    {
                        if (i > 0)
                            currentObject.Append(",");

                        currentObject.AppendFormat("{0},{1}", uvs[i].x, uvs[i].y);
                    }

                    currentObject.AppendLine();
                    currentObject.AppendLine("\t\t\t\t}");

                    currentObject.AppendLine("\t\t\tUVIndex: *" + triangleCount + " {");
                    currentObject.Append("\t\t\t\ta: ");

                    for (int i = 0; i < triangleCount; i += 3)
                    {
                        if (i > 0)
                            currentObject.Append(",");

                        currentObject.AppendFormat("{0},{1},{2}", triangles[i], triangles[i + 2], triangles[i + 1]);
                    }

                    currentObject.AppendLine();

                    currentObject.AppendLine("\t\t\t}");
                    currentObject.AppendLine("\t\t}");
                }
                #endregion

                #region "Material"
                currentObject.AppendLine("\t\tLayerElementMaterial: 0 {");
                currentObject.AppendLine("\t\t\tVersion: 101");
                currentObject.AppendLine("\t\t\tName: \"\"");
                currentObject.AppendLine("\t\t\tMappingInformationType: \"ByPolygon\"");
                currentObject.AppendLine("\t\t\tReferenceInformationType: \"IndexToDirect\"");

                int totalFaceCount = 0;

                StringBuilder submeshesSb = new StringBuilder();

                if (mesh.subMeshCount == 1)
                {

                    int numFaces = triangles.Length / 3;

                    for (int i = 0; i < numFaces; i++)
                    {
                        submeshesSb.Append("0,");
                        totalFaceCount++;
                    }

                }
                else
                {
                    List<int[]> allSubmeshes = new List<int[]>();

                    for (int i = 0; i < mesh.subMeshCount; i++)
                        allSubmeshes.Add(mesh.GetIndices(i));

                    for (int i = 0; i < triangles.Length; i += 3)
                    {

                        for (int subMeshIndex = 0; subMeshIndex < allSubmeshes.Count; subMeshIndex++)
                        {

                            for (int n = 0; n < allSubmeshes[subMeshIndex].Length; n += 3)
                            {

                                if (triangles[i] == allSubmeshes[subMeshIndex][n]
                                    && triangles[i + 1] == allSubmeshes[subMeshIndex][n + 1]
                                    && triangles[i + 2] == allSubmeshes[subMeshIndex][n + 2])
                                {

                                    submeshesSb.Append(subMeshIndex.ToString());
                                    submeshesSb.Append(",");
                                    totalFaceCount++;
                                    break;
                                }

                            }
                        }
                    }
                }

                currentObject.AppendLine("\t\t\tMaterials: *" + totalFaceCount + " {");
                currentObject.Append("\t\t\t\ta: ");
                currentObject.AppendLine(submeshesSb.ToString());
                currentObject.AppendLine("\t\t\t} ");
                currentObject.AppendLine("\t\t}");
                #endregion

                #region "Layer 0"
                currentObject.AppendLine("\t\tLayer: 0 {");
                currentObject.AppendLine("\t\t\tVersion: 100");

                currentObject.AppendLine("\t\t\tLayerElement:  {");
                currentObject.AppendLine("\t\t\t\tType: \"LayerElementNormal\"");
                currentObject.AppendLine("\t\t\t\tTypedIndex: 0");
                currentObject.AppendLine("\t\t\t}");

                if (hasColors)
                {
                    currentObject.AppendLine("\t\t\tLayerElement:  {");
                    currentObject.AppendLine("\t\t\t\tType: \"LayerElementColor\"");
                    currentObject.AppendLine("\t\t\t\tTypedIndex: 0");
                    currentObject.AppendLine("\t\t\t}");
                }

                currentObject.AppendLine("\t\t\tLayerElement:  {");
                currentObject.AppendLine("\t\t\t\tType: \"LayerElementMaterial\"");
                currentObject.AppendLine("\t\t\t\tTypedIndex: 0");
                currentObject.AppendLine("\t\t\t}");

                if (uv0)
                {
                    currentObject.AppendLine("\t\t\tLayerElement:  {");
                    currentObject.AppendLine("\t\t\t\tType: \"LayerElementUV\"");
                    currentObject.AppendLine("\t\t\t\tTypedIndex: 0");
                    currentObject.AppendLine("\t\t\t}");
                }
                currentObject.AppendLine("\t\t}");
                #endregion

                #region "Layer 1-3"
                if (uv1)
                {
                    currentObject.AppendLine("\t\tLayer: 1 {");
                    currentObject.AppendLine("\t\t\tVersion: 100");
                    currentObject.AppendLine("\t\t\tLayerElement:  {");
                    currentObject.AppendLine("\t\t\t\tType: \"LayerElementUV\"");
                    currentObject.AppendLine("\t\t\t\tTypedIndex: 1");
                    currentObject.AppendLine("\t\t\t}");
                    currentObject.AppendLine("\t\t}");
                }
                if (uv2)
                {
                    currentObject.AppendLine("\t\tLayer: 2 {");
                    currentObject.AppendLine("\t\t\tVersion: 100");
                    currentObject.AppendLine("\t\t\tLayerElement:  {");
                    currentObject.AppendLine("\t\t\t\tType: \"LayerElementUV\"");
                    currentObject.AppendLine("\t\t\t\tTypedIndex: 2");
                    currentObject.AppendLine("\t\t\t}");
                    currentObject.AppendLine("\t\t}");
                }
                if (uv3)
                {
                    currentObject.AppendLine("\t\tLayer: 3 {");
                    currentObject.AppendLine("\t\t\tVersion: 100");
                    currentObject.AppendLine("\t\t\tLayerElement:  {");
                    currentObject.AppendLine("\t\t\t\tType: \"LayerElementUV\"");
                    currentObject.AppendLine("\t\t\t\tTypedIndex: 3");
                    currentObject.AppendLine("\t\t\t}");
                    currentObject.AppendLine("\t\t}");
                }
                #endregion

                //EndLayers
                currentObject.AppendLine("\t}");

                //Connection
                StringBuilder currentConnection = new StringBuilder();

                currentConnection.AppendLine("\t;Geometry::, Model::" + ((mesh.name == null) || mesh.name == "" ? "No Name" : mesh.name));
                currentConnection.AppendLine("\tC: \"OO\"," + id + "," + parent);
                currentConnection.AppendLine();

                objects.Append(currentObject.ToString());
                connections.Append(currentConnection.ToString());

                return id;
            }

            private static string MaterialNode(string parent, string materialName, string meshName)
            {
                string id = GetUniqueID32();
                StringBuilder currentObject = new StringBuilder();

                #region "Object"
                currentObject.AppendLine("\tMaterial: " + id + ", \"Material::" + materialName + "\", \"\" {");
                currentObject.AppendLine("\t\tVersion: 102");
                currentObject.AppendLine("\t\tShadingModel: \"phong\"");
                currentObject.AppendLine("\t\tMultiLayer: 0");
                currentObject.AppendLine("\t\tProperties70:  {");
                currentObject.AppendLine("\t\t\tP: \"DiffuseColor\", \"Color\", \"\", \"A\",1,1,1");
                currentObject.AppendLine("\t\t\tP: \"Emissive\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                currentObject.AppendLine("\t\t\tP: \"Ambient\", \"Vector3D\", \"Vector\", \"\",0.2,0.2,0.2");
                currentObject.AppendLine("\t\t\tP: \"Diffuse\", \"Vector3D\", \"Vector\", \"\",0,0,0");
                currentObject.AppendLine("\t\t\tP: \"Specular\", \"Vector3D\", \"Vector\", \"\",0.2,0.2,0.2");
                currentObject.AppendLine("\t\t\tP: \"Shininess\", \"double\", \"Number\", \"\",20");
                currentObject.AppendLine("\t\t\tP: \"Opacity\", \"double\", \"Number\", \"\",1");
                currentObject.AppendLine("\t\t\tP: \"Reflectivity\", \"double\", \"Number\", \"\",0");
                currentObject.AppendLine("\t\t}");
                currentObject.AppendLine("\t}");
                #endregion
                //Connection
                StringBuilder currentConnection = new StringBuilder();

                currentConnection.AppendLine("\t;Material::01, " + materialName + ", Model::" + meshName + "\"");
                currentConnection.AppendLine("\tC: \"OO\"," + id + "," + parent);
                currentConnection.AppendLine();

                objects.Append(currentObject.ToString());
                connections.Append(currentConnection.ToString());
                return id;
            }

            private static string MorpherNode(Mesh sharedMesh, string parent)
            {
                StringBuilder currentObject = new StringBuilder();
                StringBuilder currentConnection = new StringBuilder();

                string id = GetUniqueID32();

                currentObject.AppendLine("\tDeformer: " + id + ", \"Deformer::Morpher\", \"BlendShape\" {");
                currentObject.AppendLine("\t\tVersion: 100");
                currentObject.AppendLine("\t}");

                //Connection
                currentConnection.AppendLine("\t;Deformer::Morpher, Geometry::");
                currentConnection.AppendLine("\tC: \"OO\"," + id + "," + parent);
                currentConnection.AppendLine();

                Vector3[] vertices = sharedMesh.vertices;

                for (int i = 0; i < sharedMesh.blendShapeCount; i++)
                {
                    string subDeformerID = GetUniqueID32();
                    string m_name = sharedMesh.GetBlendShapeName(i).ToString();
                    //m_name = m_name.Replace ("XXXXXXXXX", "");

                    currentObject.AppendLine("\tDeformer: " + subDeformerID + ", \"SubDeformer::" + m_name + "\", \"BlendShapeChannel\" {");
                    currentObject.AppendLine("\t\tVersion: 100");
                    currentObject.AppendLine("\t\tDeformPercent: 0");
                    currentObject.AppendLine("\t\tFullWeights: *1 {");
                    currentObject.AppendLine("\t\t\ta: 100");
                    currentObject.AppendLine("\t\t} ");
                    currentObject.AppendLine("\t}");

                    //Connection
                    currentConnection.AppendLine("\t;SubDeformer::" + m_name + ", Deformer::Morpher");
                    currentConnection.AppendLine("\tC: \"OO\"," + subDeformerID + "," + id);
                    currentConnection.AppendLine();

                    string shapeID = GetUniqueID32();

                    currentObject.AppendLine("\tGeometry: " + shapeID + ", \"Geometry::" + m_name + "\", \"Shape\" {");
                    currentObject.AppendLine("\t\tVersion: 100");

                    Vector3[] deltasVertices = new Vector3[vertices.Length];
                    Vector3[] deltasNormals = new Vector3[vertices.Length];

                    sharedMesh.GetBlendShapeFrameVertices(i, 0, deltasVertices, deltasNormals, null);

                    //if (deltasNormals == null || deltasNormals.Length != vertices.Length)
                    deltasNormals = new Vector3[vertices.Length];

                    List<int> tri = new List<int>();
                    List<Vector3> verts = new List<Vector3>();
                    List<Vector3> norms = new List<Vector3>();

                    for (int ii = 0; ii < vertices.Length; ii++)
                    {
                        if (deltasVertices[ii].sqrMagnitude > 0f || deltasNormals[ii].sqrMagnitude > 0f)
                        {
                            tri.Add(ii);
                            verts.Add(deltasVertices[ii]);
                            norms.Add(deltasNormals[ii]);
                        }
                    }

                    StringBuilder sbIndexes = new StringBuilder();
                    StringBuilder sbVertices = new StringBuilder();
                    StringBuilder sbNormals = new StringBuilder();

                    int tCount = tri.Count == 0 ? 1 : tri.Count;

                    sbIndexes.AppendLine("\t\tIndexes: *" + (tCount).ToString() + " {");
                    sbIndexes.Append("\t\t\ta: ");

                    sbVertices.AppendLine("\t\tVertices: *" + ((tCount) * 3).ToString() + " {");
                    sbVertices.Append("\t\t\ta: ");

                    sbNormals.AppendLine("\t\tNormals: *" + ((tCount) * 3).ToString() + " {");
                    sbNormals.Append("\t\t\ta: ");

                    for (int ii = 0; ii < tri.Count; ii++)
                    {
                        if (ii > 0)
                        {
                            sbIndexes.Append(",");
                        }
                        sbIndexes.Append(tri[ii].ToString());
                        sbVertices.AppendFormat("{0},{1},{2},", verts[ii].x * -1, verts[ii].y, verts[ii].z);
                        sbNormals.AppendFormat("{0},{1},{2},", norms[ii].x * -1, norms[ii].y, norms[ii].z);
                    }

                    if (tri.Count == 0)
                    {
                        sbIndexes.Append("0");
                        sbVertices.AppendFormat("{0},{1},{2},", 0, 0, 0);
                        sbNormals.AppendFormat("{0},{1},{2},", 0, 0, 0);
                        sbIndexes.Append(",");

                        sbIndexes.Append("1");
                        sbVertices.AppendFormat("{0},{1},{2},", 0, 0, 0);
                        sbNormals.AppendFormat("{0},{1},{2},", 0, 0, 0);
                        sbIndexes.Append(",")
                        ;
                        sbIndexes.Append("2");
                        sbVertices.AppendFormat("{0},{1},{2},", 0, 0, 0);
                        sbNormals.AppendFormat("{0},{1},{2},", 0, 0, 0);
                    }
                    else
                    {
                        sbVertices.Remove(sbVertices.Length - 1, 1);
                        sbNormals.Remove(sbNormals.Length - 1, 1);
                    }

                    currentObject.AppendLine(sbIndexes.ToString());
                    currentObject.AppendLine("\t\t}");

                    currentObject.AppendLine(sbVertices.ToString());
                    currentObject.AppendLine("\t\t}");

                    currentObject.AppendLine(sbNormals.ToString());
                    currentObject.AppendLine("\t\t}");

                    currentObject.AppendLine("\t}");

                    //Connection
                    currentConnection.AppendLine("\t;Geometry::" + m_name + ", SubDeformer::" + m_name);
                    currentConnection.AppendLine("\tC: \"OO\"," + shapeID + "," + subDeformerID);
                    currentConnection.AppendLine();
                }


                objects.Append(currentObject.ToString());
                connections.Append(currentConnection.ToString());
                return id;
            }

            private static void TransformNodes(Transform root, SkinnedMeshRenderer smr, string parent, out string[] boneParentIDs, bool optimizeTransforms)
            {
                var stack = root.GetAllChildren();
                var bones = smr.bones;
                for (int i = 0; i < bones.Length; i++)
                {
                    if (!stack.Contains(bones[i])) stack.Add(bones[i]);
                    if (bones[i].parent && !stack.Contains(bones[i].parent)) stack.Add(bones[i].parent);
                }
                stack.Remove(smr.transform);
                var transforms = stack.ToArray();
                var allIDs = GetUniqueIDs(transforms.Length);
                boneParentIDs = new string[smr.bones.Length];
                foreach (var item in transforms)
                {
                    var parentIndex = transforms.GetIndex(item.parent);
                    var index = transforms.GetIndex(item);
                    var parentGUID = parentIndex < 0 ? parent : allIDs[parentIndex];
                    var itemGUID = allIDs[index];
                    var isParentOfABone = bones.IsParentOfAny(item);
                    int boneIndex = smr.bones.GetIndex(item);
                    if (boneIndex > -1)
                    {
                        boneParentIDs[boneIndex] = itemGUID;
                        ModelNode(item.gameObject, ModelType.LimbNode, parentGUID, itemGUID);
                    }
                    else if (isParentOfABone || !optimizeTransforms)
                    {
                        if (!isParentOfABone && item.GetComponent<Renderer>()) continue;
                        ModelNode(item.gameObject, ModelType.NULL, parentGUID, itemGUID);
                    }
                }
            }

            private static string SkinNode(SkinnedMeshRenderer smr, string parent, string[] limbNodes)
            {
                StringBuilder currentObject = new StringBuilder();
                StringBuilder currentConnection = new StringBuilder();

                string id = GetUniqueID32();

                currentObject.AppendLine("\tDeformer: " + id + ", \"Deformer::\", \"Skin\" {");
                currentObject.AppendLine("\t\tVersion: 101");
                currentObject.AppendLine("\t\tLink_DeformAcuracy: 50");
                currentObject.AppendLine("\t}");

                //Connection
                currentConnection.AppendLine("\t;Deformer::, Geometry::");
                currentConnection.AppendLine("\tC: \"OO\"," + id + "," + parent);
                currentConnection.AppendLine();

                objects.Append(currentObject.ToString());
                connections.Append(currentConnection.ToString());

                for (int i = 0; i < smr.bones.Length; i++)
                    SkinClusterNode(id, smr, i, limbNodes[i]);

                return id;
            }

            private static string SkinClusterNode(string parent, SkinnedMeshRenderer smr, int boneIndex, string boneId)
            {
                BoneWeight[] boneWeights = smr.sharedMesh.boneWeights;
                int vertexCount = smr.sharedMesh.vertices.Length;

                List<int> indexes = new List<int>();
                List<float> weights = new List<float>();

                for (int i = 0; i < vertexCount; i++)
                {
                    if (boneWeights[i].boneIndex0 == boneIndex && boneWeights[i].weight0 > 0)
                    {
                        indexes.Add(i);
                        weights.Add(boneWeights[i].weight0);
                    }
                    if (boneWeights[i].boneIndex1 == boneIndex && boneWeights[i].weight1 > 0)
                    {
                        indexes.Add(i);
                        weights.Add(boneWeights[i].weight1);
                    }
                    if (boneWeights[i].boneIndex2 == boneIndex && boneWeights[i].weight2 > 0)
                    {
                        indexes.Add(i);
                        weights.Add(boneWeights[i].weight2);
                    }
                    if (boneWeights[i].boneIndex3 == boneIndex && boneWeights[i].weight3 > 0)
                    {
                        indexes.Add(i);
                        weights.Add(boneWeights[i].weight3);
                    }
                }

                int indexCount = indexes.Count;

                StringBuilder currentObject = new StringBuilder();
                string id = GetUniqueID32();

                currentObject.AppendLine("\tDeformer: " + id + ", \"SubDeformer::\", \"Cluster\" {");
                currentObject.AppendLine("\t\tVersion: 100");
                currentObject.AppendLine("\t\tUserData: \"\", \"\"");

                //Indexes
                currentObject.AppendLine("\t\tIndexes: *" + indexCount + " {");
                currentObject.Append("\t\t\ta: ");
                for (int i = 0; i < indexCount; i++) { if (i > 0) currentObject.Append(","); currentObject.Append(indexes[i].ToString()); }
                currentObject.AppendLine();
                currentObject.AppendLine("\t\t}");

                //Weights
                currentObject.AppendLine("\t\tWeights: *" + indexCount + " {");
                currentObject.Append("\t\t\ta: ");
                for (int i = 0; i < indexCount; i++) { if (i > 0) currentObject.Append(","); currentObject.Append(weights[i].ToString()); }
                currentObject.AppendLine();
                currentObject.AppendLine("\t\t}");

                currentObject.AppendLine("\t\tTransform: *16 {");

                Transform bone = smr.bones[boneIndex];
                Vector3 storedPosition = bone.position;
                Vector3 storedRotation = bone.rotation.eulerAngles;
                Vector3 newPosition = storedPosition;
                Vector3 newRotation = storedRotation;
                newPosition.x = -1 * newPosition.x;
                bone.position = newPosition;
                newRotation.y = newRotation.y * -1;
                newRotation.z = newRotation.z * -1;
                bone.rotation = Quaternion.Euler(newRotation);
                Matrix4x4 tm = (bone.worldToLocalMatrix * smr.localToWorldMatrix).transpose;

                currentObject.Append("\t\t\ta: ");
                currentObject.AppendFormat("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15}", tm.m00, tm.m01, tm.m02, tm.m03, tm.m10, tm.m11, tm.m12, tm.m13, tm.m20, tm.m21, tm.m22, tm.m23, tm.m30, tm.m31, tm.m32, tm.m33);
                currentObject.AppendLine();
                currentObject.AppendLine("\t\t}");

                currentObject.AppendLine("\t\tTransformLink: *16 {");

                Matrix4x4 tml = bone.worldToLocalMatrix.transpose.inverse;
                bone.position = storedPosition;
                bone.rotation = Quaternion.Euler(storedRotation);

                currentObject.Append("\t\t\ta: ");
                currentObject.AppendFormat("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15}", tml.m00, tml.m01, tml.m02, tml.m03, tml.m10, tml.m11, tml.m12, tml.m13, tml.m20, tml.m21, tml.m22, tml.m23, tml.m30, tml.m31, tml.m32, tml.m33);
                currentObject.AppendLine();
                currentObject.AppendLine("\t\t}");

                currentObject.AppendLine("\t}");

                //Connection
                StringBuilder currentConnection = new StringBuilder();

                currentConnection.AppendLine("\t;SubDeformer::, Deformer::");
                currentConnection.AppendLine("\tC: \"OO\"," + id + "," + parent);
                currentConnection.AppendLine();

                // Connect To Limb Node
                currentConnection.AppendLine("\t;" + smr.bones[boneIndex].name + "::, SubDeformer::");
                currentConnection.AppendLine("\tC: \"OO\"," + boneId + "," + id);
                currentConnection.AppendLine();

                objects.Append(currentObject.ToString());
                connections.Append(currentConnection.ToString());
                return id;
            }

        }
    }
}